package org.apache.lucene.index;

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.IOUtils;

/** Holds core readers that are shared (unchanged) when
 * SegmentReader is cloned or reopened */
final class SegmentCoreReaders {

    // Counts how many other reader share the core objects
    // (freqStream, proxStream, tis, etc.) of this reader;
    // when coreRef drops to 0, these core objects may be
    // closed.  A given instance of SegmentReader may be
    // closed, even those it shares core objects with other
    // SegmentReaders:
    private final AtomicInteger ref = new AtomicInteger(1);

    final String segment;
    final FieldInfos fieldInfos;
    final IndexInput freqStream;
    final IndexInput proxStream;
    final TermInfosReader tisNoIndex;

    final Directory dir;
    final Directory cfsDir;
    final int readBufferSize;
    final int termsIndexDivisor;

    private final SegmentReader owner;

    TermInfosReader tis;
    FieldsReader fieldsReaderOrig;
    TermVectorsReader termVectorsReaderOrig;
    CompoundFileReader cfsReader;
    CompoundFileReader storeCFSReader;

    SegmentCoreReaders(SegmentReader owner, Directory dir, SegmentInfo si, int readBufferSize, int termsIndexDivisor) throws IOException {
        segment = si.name;
        this.readBufferSize = readBufferSize;
        this.dir = dir;

        boolean success = false;

        try {
            Directory dir0 = dir;
            if (si.getUseCompoundFile()) {
                cfsReader = new CompoundFileReader(dir, IndexFileNames.segmentFileName(segment, IndexFileNames.COMPOUND_FILE_EXTENSION), readBufferSize);
                dir0 = cfsReader;
            }
            cfsDir = dir0;

            fieldInfos = new FieldInfos(cfsDir, IndexFileNames.segmentFileName(segment, IndexFileNames.FIELD_INFOS_EXTENSION));

            this.termsIndexDivisor = termsIndexDivisor;
            TermInfosReader reader = new TermInfosReader(cfsDir, segment, fieldInfos, readBufferSize, termsIndexDivisor);
            if (termsIndexDivisor == -1) {
                tisNoIndex = reader;
            } else {
                tis = reader;
                tisNoIndex = null;
            }

            // make sure that all index files have been read or are kept open
            // so that if an index update removes them we'll still have them
            freqStream = cfsDir.openInput(IndexFileNames.segmentFileName(segment, IndexFileNames.FREQ_EXTENSION), readBufferSize);

            if (fieldInfos.hasProx()) {
                proxStream = cfsDir.openInput(IndexFileNames.segmentFileName(segment, IndexFileNames.PROX_EXTENSION), readBufferSize);
            } else {
                proxStream = null;
            }
            success = true;
        } finally {
            if (!success) {
                decRef();
            }
        }

        // Must assign this at the end -- if we hit an
        // exception above core, we don't want to attempt to
        // purge the FieldCache (will hit NPE because core is
        // not assigned yet).
        this.owner = owner;
    }

    synchronized TermVectorsReader getTermVectorsReaderOrig() {
        return termVectorsReaderOrig;
    }

    synchronized FieldsReader getFieldsReaderOrig() {
        return fieldsReaderOrig;
    }

    synchronized void incRef() {
        ref.incrementAndGet();
    }

    synchronized Directory getCFSReader() {
        return cfsReader;
    }

    synchronized TermInfosReader getTermsReader() {
        if (tis != null) {
            return tis;
        } else {
            return tisNoIndex;
        }
    }

    synchronized boolean termsIndexIsLoaded() {
        return tis != null;
    }

    // NOTE: only called from IndexWriter when a near
    // real-time reader is opened, or applyDeletes is run,
    // sharing a segment that's still being merged.  This
    // method is not fully thread safe, and relies on the
    // synchronization in IndexWriter
    synchronized void loadTermsIndex(SegmentInfo si, int termsIndexDivisor) throws IOException {
        if (tis == null) {
            Directory dir0;
            if (si.getUseCompoundFile()) {
                // In some cases, we were originally opened when CFS
                // was not used, but then we are asked to open the
                // terms reader with index, the segment has switched
                // to CFS
                if (cfsReader == null) {
                    cfsReader = new CompoundFileReader(dir, IndexFileNames.segmentFileName(segment, IndexFileNames.COMPOUND_FILE_EXTENSION), readBufferSize);
                }
                dir0 = cfsReader;
            } else {
                dir0 = dir;
            }

            tis = new TermInfosReader(dir0, segment, fieldInfos, readBufferSize, termsIndexDivisor);
        }
    }

    synchronized void decRef() throws IOException {

        if (ref.decrementAndGet() == 0) {
            IOUtils.close(tis, tisNoIndex, freqStream, proxStream, termVectorsReaderOrig, fieldsReaderOrig, cfsReader, storeCFSReader);
            tis = null;
            // Now, notify any ReaderFinished listeners:
            if (owner != null) {
                owner.notifyReaderFinishedListeners();
            }
        }
    }

    synchronized void openDocStores(SegmentInfo si) throws IOException {

        assert si.name.equals(segment);

        if (fieldsReaderOrig == null) {
            final Directory storeDir;
            if (si.getDocStoreOffset() != -1) {
                if (si.getDocStoreIsCompoundFile()) {
                    assert storeCFSReader == null;
                    storeCFSReader = new CompoundFileReader(dir, IndexFileNames.segmentFileName(si.getDocStoreSegment(), IndexFileNames.COMPOUND_FILE_STORE_EXTENSION), readBufferSize);
                    storeDir = storeCFSReader;
                    assert storeDir != null;
                } else {
                    storeDir = dir;
                    assert storeDir != null;
                }
            } else if (si.getUseCompoundFile()) {
                // In some cases, we were originally opened when CFS
                // was not used, but then we are asked to open doc
                // stores after the segment has switched to CFS
                if (cfsReader == null) {
                    cfsReader = new CompoundFileReader(dir, IndexFileNames.segmentFileName(segment, IndexFileNames.COMPOUND_FILE_EXTENSION), readBufferSize);
                }
                storeDir = cfsReader;
                assert storeDir != null;
            } else {
                storeDir = dir;
                assert storeDir != null;
            }

            final String storesSegment;
            if (si.getDocStoreOffset() != -1) {
                storesSegment = si.getDocStoreSegment();
            } else {
                storesSegment = segment;
            }

            fieldsReaderOrig = new FieldsReader(storeDir, storesSegment, fieldInfos, readBufferSize, si.getDocStoreOffset(), si.docCount);

            // Verify two sources of "maxDoc" agree:
            if (si.getDocStoreOffset() == -1 && fieldsReaderOrig.size() != si.docCount) {
                throw new CorruptIndexException("doc counts differ for segment " + segment + ": fieldsReader shows " + fieldsReaderOrig.size() + " but segmentInfo shows " + si.docCount);
            }

            if (si.getHasVectors()) { // open term vector files only as needed
                termVectorsReaderOrig = new TermVectorsReader(storeDir, storesSegment, fieldInfos, readBufferSize, si.getDocStoreOffset(), si.docCount);
            }
        }
    }

    @Override
    public String toString() {
        return "SegmentCoreReader(owner=" + owner + ")";
    }
}
